<?php
// +----------------------------------------------------------------------
// | OneThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.onethink.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://www.zjzit.cn>
// +----------------------------------------------------------------------
namespace Admin\Controller;
use Think\Controller;
use Admin\Model\AuthRuleModel;
use Admin\Model\AuthGroupModel;
/**
 * 后台首页控制器
 * @author 麦当苗儿 <zuojiazi@vip.qq.com>
 */
class AdminController extends Controller {

    /**
     * 后台控制器初始化
     */
    protected function _initialize(){
        // 获取当前用户ID
        if(defined('UID')) return ;
        define('UID',is_login());
        if( !UID ){// 还没登录 跳转到登录页面
            $this->redirect('Public/login');
        }
        /* 读取数据库中的配置 */
        $config =   S('DB_CONFIG_DATA');
        if(!$config){
            $config =   api('Config/lists');
            S('DB_CONFIG_DATA',$config);
        }
        C($config); //添加配置
        C(api('Config/lists')); //添加配置

        // 是否是超级管理员
        define('IS_ROOT',   is_administrator());
        if(!IS_ROOT && C('ADMIN_ALLOW_IP')){
            // 检查IP地址访问
            if(!in_array(get_client_ip(),explode(',',C('ADMIN_ALLOW_IP')))){
                $this->error('403:禁止访问');
            }
        }
        // 检测系统权限
        if(!IS_ROOT){
            $access =   $this->accessControl(); // 判断当前操作, 是否强制允许/禁止
            if ( false === $access ) {      // 强制禁止
                $this->error('403:禁止访问');
            }elseif(null === $access ){     // 未限制
                //检测访问权限
                if ( !$this->checkRule() ){
                    // D('Member')->logout();  // 注销, 排除登录后没有权限问题
                    $this->error('未授权访问!');
                }else{
                    // 检测分类及内容有关的各项动态权限
                    $dynamic    =   $this->checkDynamic();      // 继承授权验证
                    if( false === $dynamic ){
                        $this->error(ACTION_NAME.'checkDynamic验证未授权访问!');
                    }
                }
            }else{}     // 强制允许
        }        
    }

    /**
     * 权限检测
     * @param string  $rule    检测的规则
     * @param string  $mode    check模式
     * @return boolean
     * @author 朱亚杰  <xcoolcc@gmail.com>
     */
    final protected function checkRule($rule = null, $type = AuthRuleModel::RULE_URL)
    {
        $rule = $rule ? $rule : strtolower(MODULE_NAME . '/' . CONTROLLER_NAME . '/' . ACTION_NAME);
        $mode = $_REQUEST['isChackParam'] ? 'url' : '';
        unset($_REQUEST['isChackParam']);

        static $Auth = null;
        if (!$Auth) {
            $Auth = new \Think\Auth();
        }
        if (!$Auth->check($rule, UID, $type, $mode)) {
            return false;
        }
        return true;
    }

    /**
     * 检测是否是需要动态判断的权限
     * @return boolean|null
     *      返回true则表示当前访问有权限
     *      返回false则表示当前访问无权限
     *      返回null，则表示权限不明
     *
     * @author 朱亚杰  <xcoolcc@gmail.com>
     */
    protected function checkDynamic(){}


    /**
     * action访问控制,在 **登陆成功** 后执行的第一项权限检测任务
     *
     * @return boolean|null  返回值必须使用 `===` 进行判断
     *   允许的优先级高于禁止优先级
     *   返回 **false**, 不允许任何人访问(超管除外)
     *   返回 **true**, 允许任何管理员访问,无需执行节点权限检测
     *   返回 **null**, 需要继续执行节点权限检测决定是否允许访问
     * @author 朱亚杰  <xcoolcc@gmail.com>
     */
    final protected function accessControl(){
        $allow = C('ALLOW_VISIT');              // 运行控制器对应的方法
        $allowAction = C('ALLOW_ACTION_VISIT'); // 允许方法
        $deny  = C('DENY_VISIT');               // 禁止控制器对应的方法
        $checkAction = strtolower(ACTION_NAME);
        $check = strtolower(CONTROLLER_NAME.'/'.ACTION_NAME);
        if ( ($allow || $allowAction) && (in_array_case($check,$allow) || in_array_case($checkAction,$allowAction)) ) {
            return true;
        }
        if ( !empty($deny)  && in_array_case($check,$deny) ) {
            return false;//非超管禁止访问deny中的方法
        }
        return null;//需要检测节点权限
    }

    /**
     * 对数据表中的单行或多行记录执行修改 GET参数id为数字或逗号分隔的数字
     *
     * @param string $model 模型名称,供M函数使用的参数
     * @param array  $data  修改的数据
     * @param array  $where 查询时的where()方法的参数
     * @param array  $msg   执行正确和错误的消息 array('success'=>'','error'=>'', 'url'=>'','ajax'=>false)
     *                     url为跳转页面,ajax是否ajax方式(数字则为倒数计时秒数)
     *
     * @author 朱亚杰  <zhuyajie@topthink.net>
     */
    final protected function editRow ( $model ,$data, $where , $msg ){
        $id    = array_unique((array)I('request.id',0));
        $id    = is_array($id) ? implode(',',$id) : $id;    // 数组转字符串
        //如存在id字段，则加入该条件
        $fields = M($model)->getDbFields();
        if(in_array('id',$fields) && !empty($id)){
            $where = array_merge( array('id' => array('in', $id )) ,(array)$where );
        }else{
            $pkName = I('request.pkName');
            $pkValue = I('request.pkValue');
            $pkValue    = is_array($pkValue) ? implode(',',$pkValue) : $pkValue;
            $where = array_merge( array($pkName => array('in', $pkValue )) ,(array)$where );
        }

        $msg   = array_merge( array( 'success'=>'操作成功！', 'error'=>'操作失败！', 'url'=>'' ,'ajax'=>IS_AJAX) , (array)$msg );
        if( M($model)->where($where)->save($data)!==false ) {
            $this->success($msg['success'],$msg['url'],$msg['ajax']);
        }else{
            $this->error($msg['error'],$msg['url'],$msg['ajax']);
        }
    }

    /**
     * 禁用条目
     * @param string $model 模型名称,供D函数使用的参数
     * @param array  $where 查询时的 where()方法的参数
     * @param array  $msg   执行正确和错误的消息,可以设置四个元素 array('success'=>'','error'=>'', 'url'=>'','ajax'=>false)
     *                     url为跳转页面,ajax是否ajax方式(数字则为倒数计时秒数)
     *
     * @author 朱亚杰  <zhuyajie@topthink.net>
     */
    protected function forbid ( $model , $where = array() , $msg = array( 'success'=>'状态禁用成功！', 'error'=>'状态禁用失败！')){
        $data    =  array('status' => 0);
        $this->editRow( $model , $data, $where, $msg);
    }

    /**
     * 恢复条目
     * @param string $model 模型名称,供D函数使用的参数
     * @param array  $where 查询时的where()方法的参数
     * @param array  $msg   执行正确和错误的消息 array('success'=>'','error'=>'', 'url'=>'','ajax'=>false)
     *                     url为跳转页面,ajax是否ajax方式(数字则为倒数计时秒数)
     *
     * @author 朱亚杰  <zhuyajie@topthink.net>
     */
    protected function resume (  $model , $where = array() , $msg = array( 'success'=>'状态恢复成功！', 'error'=>'状态恢复失败！')){
        $data    =  array('status' => 1);
        $this->editRow(   $model , $data, $where, $msg);
    }

    /**
     * 还原条目
     * @param string $model 模型名称,供D函数使用的参数
     * @param array  $where 查询时的where()方法的参数
     * @param array  $msg   执行正确和错误的消息 array('success'=>'','error'=>'', 'url'=>'','ajax'=>false)
     *                     url为跳转页面,ajax是否ajax方式(数字则为倒数计时秒数)
     * @author huajie  <banhuajie@163.com>
     */
    protected function restore (  $model , $where = array() , $msg = array( 'success'=>'状态还原成功！', 'error'=>'状态还原失败！')){
        $data    = array('status' => 1);
        $where   = array_merge(array('status' => -1),$where);
        $this->editRow(   $model , $data, $where, $msg);
    }

    /**
     * 条目假删除
     * @param string $model 模型名称,供D函数使用的参数
     * @param array  $where 查询时的where()方法的参数
     * @param array  $msg   执行正确和错误的消息 array('success'=>'','error'=>'', 'url'=>'','ajax'=>false)
     *                     url为跳转页面,ajax是否ajax方式(数字则为倒数计时秒数)
     *
     * @author 朱亚杰  <zhuyajie@topthink.net>
     */
    protected function delete ( $model , $where = array() , $msg = array( 'success'=>'删除成功！', 'error'=>'删除失败！')) {
        $data['status']         =   -1;
        $this->editRow( $model , $data, $where, $msg );
    }
    /**
     * 设置一条或者多条数据的状态
     */
    public function setStatus($Model=CONTROLLER_NAME){

        $ids    =   I('request.ids');
        $status =   I('request.status');
        if(empty($ids)){
            $this->error('请选择要操作的数据');
        }

        $map['id'] = array('in',$ids);
        switch ($status){
            case -1 :
                $this->delete($Model, $map, array('success'=>'删除成功','error'=>'删除失败'));
                break;
            case 0  :
                $this->forbid($Model, $map, array('success'=>'禁用成功','error'=>'禁用失败'));
                break;
            case 1  :
                $this->resume($Model, $map, array('success'=>'启用成功','error'=>'启用失败'));
                break;
            default :
                $this->error('参数错误');
                break;
        }
    }

    /**
     *  获取后台才当数据
     */
    final public function getMenus($is_hide=0, $is_rules=false){
        $where['hide']  =   $is_hide;
        if(!C('DEVELOP_MODE')){ // 是否开发者模式
            $where['is_dev']    =   0;
        }
        if($is_rules){
            $where['ar.status'] = 1;
            $where['ar.type'] = AuthRuleModel::RULE_URL;
            $prefix   = C('DB_PREFIX');
            $menus  =   M()->field("m.*, ar.id as rule_id, ar.name as rule_name")
                ->table($prefix."menu m")
                ->join($prefix."auth_rule ar ON concat('Admin/' , m.url) = ar. name", 'LEFT')
                ->where($where)
                ->order('sort asc, m.id asc')
                ->select();
        }else{
            $menus  =   M('Menu')->where($where)->field(true)->order('sort asc, id asc')->select();
        }

        return D("Common/Tree")->toTree($menus);
    }

    /**
     * 返回后台节点数据
     * @param boolean $tree    是否返回多维数组结构(生成菜单时用到),为false返回一维数组(生成权限节点时用到)
     * @retrun array
     *
     * 注意,返回的主菜单节点数组中有'controller'元素,以供区分子节点和主节点
     *
     * @author 朱亚杰 <xcoolcc@gmail.com>
     * @mender sunbirder
     */
    final protected function returnNodes($tree = true, $group_rule = null, $url_rule = null){
        static $tree_nodes = array();
        if ( $tree && !empty($tree_nodes[(int)$tree]) ) {
            return $tree_nodes[$tree];
        }
        if((int)$tree){
            $list = M('Menu')->field('id,pid,title as name,url,tip,hide')->order('sort asc')->select();
            foreach ($list as $key => $value) {
                if( stripos($value['url'],MODULE_NAME)!==0 ){
                    $list[$key]['url'] = MODULE_NAME.'/'.$value['url'];     // 菜单格式统一
                }
            }
            $nodes = list_to_tree($list,$pk='id',$pid='pid',$child='children',$root=0, $group_rule, $url_rule);
        }else{
            $nodes = M('Menu')->field('title as name,url,tip,pid')->order('sort asc')->select();
            foreach ($nodes as $key => $value) {
                if( stripos($value['url'],MODULE_NAME)!==0 ){
                    $nodes[$key]['url'] = MODULE_NAME.'/'.$value['url'];
                }
            }
        }
        $tree_nodes[(int)$tree]   = $nodes;
        return $nodes;
    }


    /**
     * 通用分页列表数据集获取方法
     *
     *  可以通过url参数传递where条件,例如:  index.html?name=asdfasdfasdfddds
     *  可以通过url空值排序字段和方式,例如: index.html?_field=id&_order=asc
     *  可以通过url参数r指定每页数据条数,例如: index.html?r=5
     *
     * @param sting|Model  $model   模型名或模型实例
     * @param array        $where   where查询条件(优先级: $where>$_REQUEST>模型设定)
     * @param array|string $order   排序条件,传入null时使用sql默认排序或模型属性(优先级最高);
     *                              请求参数中如果指定了_order和_field则据此排序(优先级第二);
     *                              否则使用$order参数(如果$order参数,且模型也没有设定过order,则取主键降序);
     *
     * @param boolean      $field   单表模型用不到该参数,要用在多表join时为field()方法指定参数
     * @author 朱亚杰 <xcoolcc@gmail.com>
     *
     * @return array|false
     * 返回数据集
     */
    protected function lists ($model,$where=array(),$order='',$field=true, $group=null, $returnAll=false){
        $options    =   array();
        $REQUEST    =   (array)I('request.');
        if(is_string($model)){
            $model  =   M($model);
        }

        $OPT        =   new \ReflectionProperty($model,'options');
        $OPT->setAccessible(true);

        $pk         =   $model->getPk();
        $pk         =   is_array($pk) && count($pk) > 0 ? current($pk) : $pk;
        if(is_null($order)){
            //order置空
        }else if ( isset($REQUEST['orderDirection']) && isset($REQUEST['orderField']) && in_array(strtolower($REQUEST['orderDirection']),array('desc','asc')) ) {
            $options['order'] = '`'.$REQUEST['orderField'].'` '.$REQUEST['orderDirection'];
        }elseif( $order==='' && empty($options['order']) && !empty($pk) ){
            $options['order'] = $pk.' desc';
        }elseif($order){
            $options['order'] = $order;
        }
        unset($REQUEST['orderDirection'],$REQUEST['orderField']);

        if($where ) $options['where'] = $where;
        $options      =   array_merge( (array)$OPT->getValue($model), $options );
        $total        =   $model->where($options['where'])->count();

        if( isset($REQUEST['pageSize']) ){
            $listRows = (int)$REQUEST['pageSize'];
        }else{
            $listRows = C('LIST_ROWS') ? C('LIST_ROWS') : 10;
        }
        $page = new \Think\Page($total, $listRows, $REQUEST);
        if(defined('CUSTOM_PAGE') && CUSTOM_PAGE && $listRows>0){
            if($total>$listRows){
                $page->setConfig('theme','%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END% %HEADER%');
            }
            $p =$page->show();
            $this->assign('_page', $p? $p: '');
            $options['limit'] = $page->firstRow.','.$page->listRows;
        }
        $pageCurrent = $REQUEST['pageCurrent'] > 0 ? (int)$REQUEST['pageCurrent'] : 1;
        $this->assign('pageSize', $listRows);
        $this->assign('pageCurrent', $pageCurrent);
        $this->assign('total',$total);

        $model->setProperty('options',$options);
        if(!empty($field)) $model->field($field);
        if($group) $model->group($group);
        $list = $model->select();
        if($returnAll){
            $result['total'] = $total;
            $result['list'] = $list;
            $list = $result;
        }
        return $list;
    }

    /**
     * 简化版列表操作, slists(Simple Lists)
     * @param $model
     * @param bool $returnAll
     * @return array|false
     */
    protected function slists ($model, $returnAll=false){
        return $this->lists($model,array(),null,null, null, $returnAll);
    }

    /**
     * 处理文档列表显示
     * @param array $list 列表数据
     * @param integer $model_id 模型id
     */
    protected function parseDocumentList($list,$model_id=null){
        $model_id = $model_id ? $model_id : 1;
        $attrList = get_model_attribute($model_id,false,'id,name,type,extra');
        // 对列表数据进行显示处理
        if(is_array($list)){
            foreach ($list as $k=>$data){
                foreach($data as $key=>$val){
                    if(isset($attrList[$key])){
                        $extra      =   $attrList[$key]['extra'];
                        $type       =   $attrList[$key]['type'];
                        if('select'== $type || 'checkbox' == $type || 'radio' == $type || 'bool' == $type) {
                            // 枚举/多选/单选/布尔型
                            $options    =   parse_field_attr($extra);
                            if($options && array_key_exists($val,$options)) {
                                $data[$key]    =   $options[$val];
                            }
                        }elseif('date'==$type){ // 日期型
                            $data[$key]    =   date('Y-m-d',$val);
                        }elseif('datetime' == $type){ // 时间型
                            $data[$key]    =   date('Y-m-d H:i',$val);
                        }
                    }
                }
                $data['model_id'] = $model_id;
                $list[$k]   =   $data;
            }
        }
        return $list;
    }
    /**
     * BJUI定制JSON消息回复处理方法
     * @param $status
     * @param $message
     * @param string $navTabId
     * @param bool $closeCurrent
     */
    protected function mtReturn($status, $message, $navTabId = "", $closeCurrent = true, $data = null)
    {
        $result = array();
        $result['statusCode'] = $status;
        $result['message'] = $message;
        $result['tabid'] = $navTabId;
        $result['forward'] = '';
        $result['forwardConfirm'] = '';
        $result['closeCurrent'] = $closeCurrent;
        $result['data'] = $data;
        $this->ajaxReturn($result);
        exit;
    }

    /**
     * 导出
     * @param string $filename
     * @param $headArr
     * @param $list
     */
    public function xlsout($filename='数据表',$headArr,$list){

        //导入PHPExcel类库，因为PHPExcel没有用命名空间，只能inport导入
        import("Org.Util.PHPExcel");
        import("Org.Util.PHPExcel.Writer.Excel5");
        import("Org.Util.PHPExcel.IOFactory.php");
        $this->getExcel($filename,$headArr,$list);
    }

    /**
     * 导出方法
     * @param $fileName
     * @param $headArr
     * @param $data
     */
    protected 	function getExcel($fileName,$headArr,$data){
        //对数据进行检验
        if(empty($data) || !is_array($data)){
            S('excelStatus', 1);
            $this->error('当前没有数据！');
        }
        //检查文件名
        if(empty($fileName)){
            S('excelStatus', 1);
            $this->error('请输入到处的文件名！');
        }

        $date = date("Y_m_d",time());
        $fileName .= "_{$date}.xls";


        //创建PHPExcel对象，注意，不能少了\
        $objPHPExcel = new \PHPExcel();
        $objProps = $objPHPExcel->getProperties();

        $key = ord("A");//A--65
        $key2 = ord("@");//@--64
        foreach($headArr as $v){
            if($key>ord("Z")){
                $key2 += 1;
                $key = ord("A");
                $colum = chr($key2).chr($key);//超过26个字母时才会启用
            }else{
                if($key2>=ord("A")){
                    $colum = chr($key2).chr($key);//超过26个字母时才会启用
                }else{
                    $colum = chr($key);
                }
            }
            $objPHPExcel->setActiveSheetIndex(0) ->setCellValue($colum.'1', $v);
            $key += 1;

        }

        $column = 2;
        $objActSheet = $objPHPExcel->getActiveSheet();
        //设置为文本格式
        foreach($data as $key => $rows){ //行写入
            $span = ord("A");
            $span2 = ord("@");
            foreach($rows as $keyName=>$value){// 列写入

                if($span>ord("Z")){
                    $span2 += 1;
                    $span = ord("A");
                    $j = chr($span2).chr($span);//超过26个字母时才会启用
                }else{
                    if($span2>=ord("A")){
                        $j = chr($span2).chr($span);//超过26个字母时才会启用
                    }else{
                        $j = chr($span);
                    }
                }
                $objActSheet->setCellValueExplicit($j.$column, $value);
                $span++;
            }
            $column++;
        }

        $fileName = iconv("utf-8", "gb2312", $fileName);
        //重命名表
        // $objPHPExcel->getActiveSheet()->setTitle('test');
        //设置活动单指数到第一个表,所以Excel打开这是第一个表
        $objPHPExcel->setActiveSheetIndex(0);
        ob_end_clean();//清除缓冲区,避免乱码
        header('Content-Type: application/vnd.ms-excel');
        header("Content-Disposition: attachment;filename=\"$fileName\"");
        header('Cache-Control: max-age=0');
        $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
        $objWriter->save('php://output'); //文件通过浏览器下载
        S('excelStatus', 1);
        exit;
    }

    /**
     * 获取导出进度数据
     */
    function getExportProgress(){
        $resultData = array();
        $resultData['excelNumber'] = S('excelNumber');
        $resultData['excelStatus'] = S('excelStatus');
        $resultData['excelCount'] = S('excelCount');
        $resultData['export_num'] = S('export_num');
        $resultData['read_num'] = S('read_num');
        die(json_encode($resultData));
    }


    /**
     * 导出提示弹窗
     */
    public function exportDialog(){
        $this->display('Public/Dialog/exportDialog');
    }

}
